Exceptions

Exceptions which are events that can modify the flow of control through a program.

In Python, exceptions are triggered automatically on errors, and they can be triggered and intercepted by your code.

They are processed by four statements we’ll study in this notebook, the first of which has two variations (listed separately here) and the last of which was an optional extension until Python 2.6 and 3.0:

  • try/except:

    • Catch and recover from exceptions raised by Python, or by you
  • try/finally:

    • Perform cleanup actions, whether exceptions occur or not.
  • raise:

    • Trigger an exception manually in your code.
  • assert:

    • Conditionally trigger an exception in your code.
  • with/as:

    • Implement context managers in Python 2.6, 3.0, and later (optional in 2.5).

try/except Statement

try:
    statements           # Run this main action first
except name1:       
  # Run if name1 is raised during try block
    statements
except (name2, name3):   
   # Run if any of these exceptions occur
    statements 
except name4 as var:     
     # Run if name4 is raised, assign instance raised to var 
    statements
except:                  # Run for all other exceptions raised
    statements
else:
    statements           # Run if no exception was raised during try block

In [1]:
list_of_numbers = [number for number in range(1, 100)]
print(list_of_numbers)


[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]

In [10]:
dictionary_of_numbers = {}
for number in list_of_numbers:
    dictionary_of_numbers[number**2] = number
    
try:
    index = list_of_numbers.index(2)
    value = dictionary_of_numbers[index]
except (ValueError, KeyError):
    print('Error Raised, but Controlled! ')
else: 
    # This executes ONLY if no exception is raised
    print('Getting number at position %d : %d' % (index, value))
finally:
    # Do cleanup operations
    print('Cleaning UP')


Getting number at position 1 : 1
Cleaning UP

try/finally Statement

The other flavor of the try statement is a specialization that has to do with finalization (a.k.a. termination) actions. If a finally clause is included in a try, Python will always run its block of statements “on the way out” of the try statement, whether an exception occurred while the try block was running or not.

In it's general form, it is:

try:
    statements # Run this action first 
finally:
    statements # Always run this code on the way out

with/as Context Managers

Python 2.6 and 3.0 introduced a new exception-related statement—the with, and its optional as clause. This statement is designed to work with context manager objects, which support a new method-based protocol, similar in spirit to the way that iteration tools work with methods of the iteration protocol.

Context Manager Intro

Basic Usage:

with expression [as variable]: 
    with-block

Classical Usage

with open(r'C:\misc\data') as myfile: 
    for line in myfile:
        print(line)
    # ...more code here...

... even using multiple context managers:

with open('script1.py') as f1, open('script2.py') as f2: 
    for (linenum, (line1, line2)) in enumerate(zip(f1, f2)):
        if line1 != line2:
            print('%s\n%r\n%r' % (linenum, line1, line2))

How it works

  1. The expression is evaluated,resulting in an object known as a context manager that must have __enter__ and __exit__ methods

  2. The context manager’s __enter__ method is called. The value it returns is assigned to the variable in the as clause if present, or simply discarded otherwise

  3. The code in the nested with block is executed.

  4. If the with block raises an exception, the __exit__(type,value,traceback) method is called with the exception details. These are the same three values returned by sys.exc_info (Python function). If this method returns a false value, the exception is re-raised; otherwise, the exception is terminated. The exception should normally be reraised so that it is propagated outside the with statement.

  5. If the with block does not raise an exception, the __exit__ method is still called, but its type, value, and traceback arguments are all passed in as None.

Usage with Exceptions


In [4]:
class TraceBlock:
    def message(self, arg):
        print('running ' + arg) 
        
    def __enter__(self):
        print('starting with block')
        return self
    
    def __exit__(self, exc_type, exc_value, exc_tb):
        if exc_type is None: 
            print('exited normally\n')
        else:
            print('raise an exception! ' + str(exc_type)) 
            return False # Propagate

In [5]:
with TraceBlock() as action: 
    action.message('test 1')
    print('reached')


starting with block
running test 1
reached
exited normally


In [6]:
with TraceBlock() as action: 
    action.message('test 2') 
    raise TypeError()
    print('not reached')


starting with block
running test 2
raise an exception! <class 'TypeError'>
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-4dfc52b66e0b> in <module>()
      1 with TraceBlock() as action:
      2     action.message('test 2')
----> 3     raise TypeError()
      4     print('not reached')

TypeError: 

User Defined Exceptions


In [1]:
class AlreadyGotOne(Exception): 
    pass

def gail():
    raise AlreadyGotOne()

In [2]:
try:
    gail()
except AlreadyGotOne:
    print('got exception')


got exception

In [2]:
class Career(Exception):
    
    def __init__(self, job, *args, **kwargs):
        super(Career, self).__init__(*args, **kwargs)
        self._job = job
    
    def __str__(self): 
        return 'So I became a waiter of {}'.format(self._job)
    
raise Career('Engineer')


---------------------------------------------------------------------------
Career                                    Traceback (most recent call last)
<ipython-input-2-d663e503fce1> in <module>()
      8         return 'So I became a waiter of {}'.format(self._job)
      9 
---> 10 raise Career('Engineer')

Career: So I became a waiter of Engineer